package visitrecord

import (
	"context"
	"errors"
	"fmt"
	"gitee.com/chunanyong/zorm"
	"gitee.com/xinshortlink/internal/constants"
	"gitee.com/xinshortlink/internal/dal/db/dao"
	"gitee.com/xinshortlink/internal/dal/db/model"
	"gitee.com/xinshortlink/internal/dto"
	"gitee.com/xinshortlink/internal/dto/pb"
	"gitee.com/xinshortlink/internal/pkg/mq"
	"gitee.com/xinshortlink/internal/pkg/utils"
	"github.com/avast/retry-go/v4"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
	"net/http"
	"sync"
	"time"
)

const newVisitorCookieKey = "visitor"

type Service struct {
	sync.Mutex

	dao           *dao.VisitRecordDao
	linkDao       *dao.LinkDao
	shortRouteDao *dao.ShortRouteDao
	buffer        *pb.VisitRecordMessages // 缓冲队列
	bufferSize    int                     // 最大数量
	flushInterval time.Duration           // 刷新间隔
	mq            mq.MQ
	logger        *zap.Logger
}

func NewService(mq mq.MQ, logger *zap.Logger, dao *dao.VisitRecordDao, linkDao *dao.LinkDao) *Service {
	s := &Service{
		dao:           dao,
		linkDao:       linkDao,
		buffer:        new(pb.VisitRecordMessages),
		bufferSize:    256,
		flushInterval: time.Second,
		mq:            mq,
		logger:        logger,
	}

	// 注册 MQ 消费回调函数
	s.mq.Register(func(ctx context.Context, message []byte) error {
		messages := new(pb.VisitRecordMessages)
		if err := proto.Unmarshal(message, messages); err != nil {
			return err
		}

		if len(messages.Messages) == 0 {
			return errors.New("message empty")
		}

		vrs := make([]*model.VisitRecordStruct, len(messages.Messages))

		var shortClickCount = make(map[string]int, 0)

		for i, msg := range messages.Messages {
			visitTime := time.Unix(msg.VisitTime, 0)

			vrs[i] = &model.VisitRecordStruct{
				ShortId:       msg.ShortId,
				VisitTime:     visitTime,
				ClientIp:      msg.ClientIp,
				CountryCode:   msg.CountryCode,
				Region:        msg.Region,
				DeviceType:    msg.DeviceType,
				Os:            msg.Os,
				Browser:       msg.Browser,
				Network:       msg.Network,
				RefererDomain: msg.RefererDomain,
				IsNew:         int8(msg.IsNew),
			}

			if count, ok := shortClickCount[msg.ShortId]; ok {
				shortClickCount[msg.ShortId] = count + 1
			} else {
				shortClickCount[msg.ShortId] = 1
			}

		}

		_, err := zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {

			err := s.dao.CreateBatch(ctx, vrs)

			for shortId, count := range shortClickCount {
				gid, err := s.shortRouteDao.FindGidByShortId(ctx, shortId)
				if err != nil {
					return nil, err
				}

				if gid == 0 {
					s.logger.Warn("短链接路由表不存在", zap.String("shortId", shortId))
					return nil, nil
				}

				err = s.linkDao.UpdateClickNum(ctx, shortId, gid, count)
				if err != nil {
					return nil, err
				}
			}

			return nil, err
		})

		return err
	})

	// 自动刷新缓冲区 发送消息
	go s.autoFlush()

	return s
}

func (s *Service) GetVisitRecord(ctx context.Context, req *dto.VisitRecordReq) (*dto.PageResp[*dto.VisitRecordDTO], error) {
	page, list, err := s.dao.FindVisitRecord(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("SERVICE: FindOriginUrl fail %w", err)
	}
	records := make([]*dto.VisitRecordDTO, len(list))
	for i, v := range list {
		records[i] = &dto.VisitRecordDTO{
			Id:            v.Id,
			ShortId:       v.ShortId,
			VisitTime:     v.VisitTime,
			ClientIp:      v.ClientIp,
			CountryCode:   v.CountryCode,
			Region:        v.Region,
			DeviceType:    v.DeviceType,
			Os:            v.Os,
			Browser:       v.Browser,
			Network:       v.Network,
			RefererDomain: v.RefererDomain,
			IsNew:         v.IsNew,
		}
	}

	return &dto.PageResp[*dto.VisitRecordDTO]{
		Records:    records,
		TotalCount: page.TotalCount,
		PageCount:  page.PageCount,
		FirstPage:  page.FirstPage,
		HasPrev:    page.HasPrev,
		HasNext:    page.HasNext,
		LastPage:   page.LastPage,
	}, nil

}

// CreateVisitRecord 创建短链接访问记录
func (s *Service) CreateVisitRecord(ctx *gin.Context, shortId string) error {
	// TODO 网络类型，IP归属定等，通过收费的IP服务获取。

	domain := ctx.Request.Host
	ip := ctx.ClientIP()
	ua := ctx.Request.Header.Get("user-agent")
	os, browser, deviceType := utils.ParseUserAgent(ua)

	// 判断是否为新用户
	// cookie 中存在则为老访客。不存在则为新访客。

	var isNew int8 = constants.NO
	var visitor string
	// 读取 cookie
	cookie, err := ctx.Request.Cookie(newVisitorCookieKey)
	if err != nil {
		if !errors.Is(err, http.ErrNoCookie) {
			return fmt.Errorf("SERVICE: CreateVisitRecord fail %w", err)
		}
	}

	if cookie == nil {
		isNew = constants.YES
		UUID := uuid.New()
		visitor = UUID.String()
		ctx.SetCookie(newVisitorCookieKey, visitor, 30*3600*24, ctx.Request.URL.String(), "d.cc", false, true)
	}

	// 提交访问记录
	s.add(&pb.VisitRecordMessage{
		ShortId:       shortId,
		VisitTime:     time.Now().Unix(),
		ClientIp:      ip,
		CountryCode:   "CN",
		Region:        "BJ",
		DeviceType:    deviceType,
		Os:            os,
		Browser:       browser,
		Network:       "WIFI",
		RefererDomain: domain,
		IsNew:         int32(isNew),
	})

	return nil

	//_, err = zorm.Transaction(ctx, func(ctx context.Context) (interface{}, error) {
	//	err := s.visitRecordDao.Create(ctx, &model.VisitRecordStruct{
	//		ShortId:       shortId,
	//		VisitTime:     time.Now(),
	//		ClientIp:      ip,
	//		CountryCode:   "CN",
	//		Region:        "BJ",
	//		DeviceType:    deviceType,
	//		Os:            os,
	//		Browser:       browser,
	//		Network:       "WIFI",
	//		RefererDomain: domain,
	//		IsNew:         isNew,
	//	})
	//
	//	if err != nil {
	//		return nil, err
	//	}
	//
	//	short := &model.LinkStruct{}
	//	finder := zorm.NewSelectFinder(model.LinkStructTableName).Append("WHERE short_id = ?", shortId)
	//	_, err = zorm.QueryRow(ctx, finder, short)
	//	if err != nil {
	//		return nil, err
	//	}
	//
	//	short.ClickNum = short.ClickNum + 1
	//	_, err = zorm.Update(ctx, short)
	//	return nil, err
	//})
	//
	//if err != nil {
	//	return fmt.Errorf("SERVICE: CreateVisitRecord fail %w", err)
	//}
	//return nil
}

// add 添加到缓冲区
func (s *Service) add(message *pb.VisitRecordMessage) {
	s.Lock()
	defer s.Unlock()

	s.buffer.Messages = append(s.buffer.Messages, message)
	if len(s.buffer.Messages) >= s.bufferSize {
		go s.flush()
	}
}

// flush 刷新缓冲区
// 发送消息队列
func (s *Service) flush() {
	s.Lock()
	defer s.Unlock()

	if len(s.buffer.Messages) == 0 {
		return
	}

	f := func() error {
		bytes, err := proto.Marshal(s.buffer)
		if err != nil {
			s.logger.Error("SERVICE: 序列化消息失败", zap.Error(err))
			return fmt.Errorf("SERVICE: 序列化消息失败 :%w", err)
		}

		s.logger.Info("send mq message")
		return s.mq.Send(bytes)
	}

	err := retry.Do(f, retry.Attempts(3), retry.DelayType(retry.BackOffDelay))
	if err != nil {
		s.logger.Error("SERVICE: 发送 MQ 消息失败", zap.Error(err))

	}

	s.buffer.Messages = nil
}

// autoFlush 自动刷新缓冲区
func (s *Service) autoFlush() {
	ticker := time.NewTicker(time.Second)

	for range ticker.C {
		s.flush()
	}
}
